<?php
namespace filecache;
class PHPFileCacheComposer{
    private  $cache_dir_path="";//存储文件夹路径
    private static $fp_lock=[]; //静态类型存放锁后的文件对象
    private static $clear_expires_cache_file_requests_time=50000; //大概请求多少次清理一次过期缓存（填0是100%，填10是1/10的概率，填1000是1/1000的概率,-1为不自动清除过期文件）
    private  $function_info=[]; //存储方法数据
    private $lock_out_time=0; //当锁卡死时多少秒放弃争夺 0秒为无限时长

    private $module_name="";
    public function __construct($module_name="")
    {
        $this->module_name=self::filterModuleName($module_name);
        $this->setCacheDirPath($this->module_name);
        $this->howManyRequestsClearExpiresCacheFile();
        $this->creatCacheDir();
    }

    public function __destruct()
    {
        $this->clearMyClassKeyLock();

    }


    private function setCacheDirPath($module_name){
        if (empty($this->cache_dir_path)){
            $dir_path="./FileCache";
            $this->cache_dir_path=$dir_path;
        }
        if (!empty($module_name)){
            $this->cache_dir_path=$this->cache_dir_path."/".$module_name;
        }
        $this->createFolders($this->cache_dir_path);
    }

    private function createFolders($dir){
        return is_dir($dir) or ( $this->createFolders(dirname($dir)) and mkdir($dir, 0777));
    }


    private  function creatCacheDir(){
        $dir_path=$this->cache_dir_path;
        if(!file_exists($dir_path) || !is_dir($dir_path)){
            mkdir($dir_path);
        }
        $this->cache_dir_path=$dir_path;
    }

    private  function creatCacheFile($key=null){
        $key=self::filterKeyName($key);
        $file_path=$this->cache_dir_path."/".md5($key).md5(base64_encode($key)).".cache";
        if ($key==null){
            $file_path=$this->cache_dir_path."/NoKey.cache";
        }
        if(!file_exists($file_path)){
            $myfile = fopen($file_path, "w");
            flock($myfile,LOCK_EX);
            fwrite($myfile,"");
            flock($myfile,LOCK_UN);
            fclose($myfile);
        }
        return $file_path;
    }
    private function clearMyClassKeyLock(){
        if (!empty($this->function_info["myClassLockKey"][$this->module_name]) && is_array($this->function_info["myClassLockKey"][$this->module_name])){
            foreach ($this->function_info["myClassLockKey"][$this->module_name] as $k=>$v){
                flock(self::$fp_lock[$this->module_name][$k],LOCK_UN);
                fclose(self::$fp_lock[$this->module_name][$k]);
                unset(self::$fp_lock[$this->module_name][$k]);
                unset($this->function_info["myClassLockKey"][$this->module_name][$k]);
            }
        }
    }

    private function howManyRequestsClearExpiresCacheFile(){
        if (is_int(self::$clear_expires_cache_file_requests_time) && self::$clear_expires_cache_file_requests_time>=0){
            $rand=mt_rand(0,self::$clear_expires_cache_file_requests_time);
            if ($rand==self::$clear_expires_cache_file_requests_time){
                $this->clearExpiresCacheFile();
            }
        }
    }



    public function setLockOutTime($lock_out_time=0){
        if (is_int($lock_out_time)){
            $this->lock_out_time=$lock_out_time;
        }
        return $this;
    }
    public function lockKey($key){
        $key=self::filterKeyName($key);
        $file_path= $this->creatCacheFile($key);
        if (empty(self::$fp_lock[$this->module_name][$key])){
            self::$fp_lock[$this->module_name][$key] = fopen($file_path,'r+');
        }
        $in_function_time=strtotime('now');
        while (1){
            if(flock(self::$fp_lock[$this->module_name][$key],LOCK_EX)){
                $this->function_info["myClassLockKey"][$this->module_name][$key]=1;
                return true;
            }else{
                if($this->lock_out_time!=0 && is_int($this->lock_out_time)){
                    if (strtotime('now')>$in_function_time+$this->lock_out_time){
                        $this->function_info['error_arr'][__METHOD__][]="超时！抢不到key。";
                        return false;
                    }
                }
            }
        }
    }
    public function getHasLock($key=null){
        $key=self::filterKeyName($key);
        if (empty(self::$fp_lock[$this->module_name][$key])){
            if (!$this->lockKey($key)) return false;
        }
        $file_path=$this->cache_dir_path."/".md5($key).md5(base64_encode($key)).".cache";
        if ($key==null){
            $file_path=$this->cache_dir_path."/NoKey.cache";
        }
        $filesize=filesize($file_path);
        if ($filesize<=0){
            return "";
        }
        //指针指回文件头
        fseek(self::$fp_lock[$this->module_name][$key],0);
        $content="";
        while(!feof(self::$fp_lock[$this->module_name][$key])) {
            $content.= fgetc(self::$fp_lock[$this->module_name][$key]);
        }
        if (is_null(json_decode($content,1))) {
            $this->function_info['error_arr'][__METHOD__][]="文件内容不是json";
            return false;
        }
        $content=json_decode($content,1);
        if (!empty($content['expires_time'])&&is_int($content['expires_time'])){
            if ($content['expires_time']<strtotime('now')){
                flock(self::$fp_lock[$this->module_name][$key],LOCK_UN);
                fclose(self::$fp_lock[$this->module_name][$key]);
                unset(self::$fp_lock[$this->module_name][$key]);
                unlink($file_path);
                $this->function_info['error_arr'][__METHOD__][]=$key." 已经过期";
                return false;
            }
        }
        return $content['data'];
    }

    public function setHasLock($key=null,$value=null,$time=null){
        $key=self::filterKeyName($key);
        if (empty(self::$fp_lock[$this->module_name][$key])){
            if (!$this->lockKey($key)) return false;
        }
        if (!empty(self::$fp_lock[$this->module_name][$key])){
            $expires_time=null;
            if(!empty($time) && is_int($time)){
                $expires_time=strtotime('now')+$time;
            }
            $content=[];
            $content['data']=$value;
            $content['expires_time']=$expires_time;
            $content['set_token']=date('YmdHis',strtotime('now')).rand(1,99999);
            $content['key']=$key;
            fseek(self::$fp_lock[$this->module_name][$key],0); //文件指针返回
            ftruncate(self::$fp_lock[$this->module_name][$key],0); //清除
            //  var_dump(json_encode($content));
            return fwrite(self::$fp_lock[$this->module_name][$key], json_encode($content));
        }
        $this->function_info['error_arr'][__METHOD__][]="set不得无锁";
        return false;
    }



    public function clearKeyLock($key){
        $key=self::filterKeyName($key);
        flock(self::$fp_lock[$this->module_name][$key],LOCK_UN);
        fclose(self::$fp_lock[$this->module_name][$key]);
        unset(self::$fp_lock[$this->module_name][$key]);
        unset($this->function_info["myClassLockKey"][$this->module_name][$key]);
    }

    public static function clearAllLock(){
        if (!empty(self::$fp_lock) && is_array(self::$fp_lock))
        {

            foreach (self::$fp_lock as $model_k => $model_v){
                foreach ($model_v as $lock_k=>$lock_v){
                    flock($lock_v,LOCK_UN);
                    fclose($lock_v);
                    unset(self::$fp_lock[$model_k][$lock_k]);
                }
            }



        }
    }

    //清除过期缓存文件
    public function clearExpiresCacheFile(){
        if (is_dir($this->cache_dir_path)){
            $dir_list=scandir($this->cache_dir_path);
            $lock_key_file=[];
            foreach (self::$fp_lock[$this->module_name] as $lock_k=>$lock_v){
                $lock_key_file[]=md5($lock_k).md5(base64_encode($lock_k)).".cache";
            }
            foreach ($dir_list as $k=>$v){
                if ($v=='.'||$v=='..') continue;
                if (in_array($v,$lock_key_file)) continue;
                $content=file_get_contents($this->cache_dir_path."/".$v);
                $content_arr=json_decode($content,1);
                if (is_null($content_arr)) {
                    $this->function_info['error_arr'][__METHOD__][]="文件内容不是json";
                    return false;
                }
                if (!empty($content_arr['expires_time']) && $content_arr['expires_time']<strtotime('now')){
                    unlink($this->cache_dir_path."/".$v);
                }
            }
            return true;
        }
        $this->function_info['error_arr'][__METHOD__][]="找不到要清理的文件夹";
        return false;

    }

    public function getError(){
        if (!empty($this->function_info['error_arr'])){
            return $this->function_info['error_arr'];
        }
        return "";
    }

    private static function filterKeyName($key){
        $type=gettype($key);
        $to_json_type=[
            "boolean","array","object","resource"
        ];
        if (in_array($type,$to_json_type)){
            if ($type=="object"){
                $key=array_keys((array)$key);
            }
            if ($type=="resource"){
                $key=(array)$key;
            }
            $key=json_encode($key);
        }
        return (String)$key;
    }

    private static function filterModuleName($module_name){
        $type=gettype($module_name);
        $to_json_type=[
            "boolean","array","object","resource"
        ];
        if (in_array($type,$to_json_type)){
            if ($type=="object"){
                $module_name=array_keys((array)$module_name);
            }
            if ($type=="resource"){
                $module_name=(array)$module_name;
            }
            $module_name=json_encode($module_name);
            $module_name=md5($module_name);
        }
        return (String)$module_name;
    }




}